package com.ghc.filter;

import com.ghc.feign.UserFeignClient;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 定义全局过滤器， 会对所有路由生效
 */
@Data
@Slf4j
@Component
@RefreshScope
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "gateway.filter")
public class BlackListFilter implements GlobalFilter, Ordered {

    private final UserFeignClient userFeignClient;

    public static final String WARNING_MSG = "您频繁进⾏注册，请求已被拒绝";
    public static final String NO_PASS_MSG = "token验证未通过";
    public static final String TOKEN_NULL = "token不能为空";

    public static final Map<String, Bucket> LOCAL_CACHE = new ConcurrentHashMap<>();

    // 令牌桶初始容量
    private Long capacity ;

    // 补充桶的时间间隔，即2秒补充一次
    private Long seconds ;

    // 每次补充token的个数
    private Long refillTokens ;

    //不需要token的服务
    private Map<String, List<String>> tokenNoCheck;



    /*// 模拟黑名单
    private static final List<String> blackList = new ArrayList<>();

    static {
        //本机地址
        blackList.add("192.0.0.0");
    }*/

    /**
     * 过滤器核心方法
     * @param exchange 封装了request、response对象上下文
     * @param chain 网关过滤器 （包含全局过滤器和单路由过滤器）
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //思路： 获取客户端ip， 判断是否在黑名单中， 在的话就拒绝访问， 不在的话就放行
        //从上下文中取出request、response对象
        /*ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String clientIp = request.getRemoteAddress().getHostString();
        if (blackList.contains(clientIp)){
            //拒绝访问， 返回
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
            log.debug("===>IP,{}  在黑名单中 ， 将拒绝访问", clientIp);
            String date = "Request be denied!";
            DataBuffer wrap = response.bufferFactory().wrap(date.getBytes());
            return response.writeWith(Mono.just(wrap));
        }
        return chain.filter(exchange);*/
        //  ip令牌桶限流
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String ip = Objects.requireNonNull(request.getRemoteAddress()).getHostString();
        log.info("访问IP为:{}", ip);
        Bucket bucket = LOCAL_CACHE.computeIfAbsent(ip, k -> createNewBucket());
        log.info("IP:{} ,令牌通可用的Token数量:{} ", ip, bucket.getAvailableTokens());
        if (bucket.tryConsume(1)) {
            // 过滤不需要token验证的路由。 需要验证的调用user微服务进行验证
            Route route = exchange.getAttribute("org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute");
            //route.getUri().getAuthority()  微服务名称
            if (route != null && tokenNoCheck != null && tokenNoCheck.get(route.getUri().getAuthority()) != null){
                List<String> path = tokenNoCheck.get(route.getUri().getAuthority());
                if (path.size() == 0){
                    return chain.filter(exchange);
                }
                for (String s : path) {
                    if (request.getPath().value().startsWith(s)){
                        return chain.filter(exchange);
                    }
                }
                List<String> tokens = request.getHeaders().get("token");
                String token ;
                if (tokens == null || tokens.size() == 0){
                    byte[] bits = TOKEN_NULL.getBytes(StandardCharsets.UTF_8);
                    DataBuffer buffer = response.bufferFactory().wrap(bits);
                    //指定编码，否则在浏览器中会中文乱码
                    response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
                    response.setStatusCode(HttpStatus.FORBIDDEN);
                    return response.writeWith(Mono.just(buffer));
                }
                token = tokens.get(0);
                // 进行token验证
                String info = userFeignClient.info(token);
                if (info != null && !info.equals("")){
                    return chain.filter(exchange);
                }
            }
            byte[] bits = NO_PASS_MSG.getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            //指定编码，否则在浏览器中会中文乱码
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.writeWith(Mono.just(buffer));
        } else {
            byte[] bits = WARNING_MSG.getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            //指定编码，否则在浏览器中会中文乱码
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            response.setStatusCode(HttpStatus.SEE_OTHER);
            return response.writeWith(Mono.just(buffer));
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }

    private Bucket createNewBucket() {
        Duration refillDuration = Duration.ofSeconds(seconds);
        Refill refill = Refill.of(refillTokens, refillDuration);
        Bandwidth limit = Bandwidth.classic(capacity, refill);
        return Bucket4j.builder().addLimit(limit).build();
    }

}
